﻿using ClockInLearning.Db;
using ClockInLearning.Dto;
using ClockInLearning.Tools;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ClockInLearning.Impl
{
    public class PointsRegistApplication
    {
        private readonly AppDbContext _context;

        public PointsRegistApplication(AppDbContext context)
        {
            _context = context;
        }

        public async Task<ServiceResult> RegistAddRecord(PointsRegistRecordInputDto dto)
        {
            dto.OperationType = OperationTypeEnum.Add;
            var userInfo = _context.GetUserInfoByToken<UserInfo>();
            dto.RegisterId = userInfo.Id;
            if (dto.RegistType == RegistTypeEnum.Privte)
            {
                dto.UserId = dto.RegisterId;
            }
            else
            {
                userInfo = _context.UserInfos.Where(x => x.Id == dto.UserId).FirstOrDefault();
            }
            dto.RegistTime = DateTime.Now;
            return await RegistRecord(dto, userInfo);
        }

        public async Task<ServiceResult> RegistUseRecord(UsedPointRegistDto dto)
        {
            var result = new ServiceResult();

            var userInfo = _context.GetUserInfoByToken<UserInfo>();
            var baseObj = new PointsRegistRecord()
            {
                UserId = userInfo.Id,
                RegisterId = userInfo.Id,
                OperationType = OperationTypeEnum.Use,
                RegistTime = DateTime.Now,
                Remark = dto.Remark
            };

            if (dto.PrivatePointsRegistDuration > 0)
            {
                var privateUsedObj = baseObj.SimpleMapTo<PointsRegistRecord>();
                privateUsedObj.Id = Guid.NewGuid();
                privateUsedObj.RegistType = RegistTypeEnum.Privte;
                privateUsedObj.RegistDuration = dto.PrivatePointsRegistDuration;
                privateUsedObj.Points = dto.PrivatePoints;
                await _context.AddAsync(privateUsedObj.SimpleMapTo<PointsRegistRecord>());
                userInfo.CumulativePlayedTime += dto.PrivatePointsRegistDuration;
                userInfo.PrivatePoints -= dto.PrivatePoints;
            }

            if (dto.RecognizedPointsRegistDuration > 0)
            {
                var superviseUsedObj = baseObj.SimpleMapTo<PointsRegistRecord>();
                superviseUsedObj.Id = Guid.NewGuid();
                superviseUsedObj.RegistType = RegistTypeEnum.Supervise;
                superviseUsedObj.RegistDuration = dto.RecognizedPointsRegistDuration;
                superviseUsedObj.Points = dto.RecognizedPoints;
                await _context.AddAsync(superviseUsedObj.SimpleMapTo<PointsRegistRecord>());
                userInfo.CumulativePlayedTime += dto.RecognizedPointsRegistDuration;
                userInfo.RecognizedPoints -= dto.RecognizedPoints;
            }
            await _context.SaveChangesAsync();

            return result;
        }

        private async Task<ServiceResult> RegistRecord(PointsRegistRecordInputDto dto, UserInfo userInfo)
        {
            var result = new ServiceResult();

            dto.Id = Guid.NewGuid();
            await _context.AddAsync(dto.SimpleMapTo<PointsRegistRecord>());
            if (dto.RegistType == RegistTypeEnum.Privte)
            {
                if (dto.OperationType == OperationTypeEnum.Add)
                {
                    userInfo.PrivatePoints += dto.Points;
                    userInfo.HisPrivatePoints += dto.Points;
                    userInfo.CumulativeLearnedTime += dto.RegistDuration;
                }
                else
                {
                    userInfo.PrivatePoints -= dto.Points;
                    userInfo.CumulativePlayedTime += dto.RegistDuration;
                }
            }
            else
            {
                if (dto.OperationType == OperationTypeEnum.Add)
                {
                    userInfo.RecognizedPoints += dto.Points;
                    userInfo.HisRecognizedPoints += dto.Points;
                    userInfo.CumulativeLearnedTime += dto.RegistDuration;
                }
                else
                {
                    userInfo.RecognizedPoints -= dto.Points;
                    userInfo.CumulativePlayedTime += dto.RegistDuration;
                }
            }
            await _context.SaveChangesAsync();

            return result;
        }

        public async Task<ServiceResult<PageData<PointsRegistRecordOutputDto>>> Query(PageRequest<PointsRegistRecordQueryDto> request)
        {
            var result = new ServiceResult<PageData<PointsRegistRecordOutputDto>>();

            var pageData = _context.PointsRegistRecords
                  .Where(x => x.UserId == request.QueryDto.UserId)
                  .ToPage<PointsRegistRecord, PointsRegistRecordOutputDto>(request.PageCondition)
                  .ToPageData();

            result.IsSuccess(pageData);
            return result;
        }

        public async Task<ServiceResult<List<CalendarRegistRecordDto>>> QueryListByMonth(QueryMonthDto dto)
        {
            var result = new ServiceResult<List<CalendarRegistRecordDto>>();

            var userInfo = _context.GetUserInfoByToken<UserInfo>();

            var list = _context.PointsRegistRecords
                .Where(x => x.RegistTime.Year == dto.Year && x.RegistTime.Month == dto.Month && x.UserId == userInfo.Id)?
                .Select(x => x.SimpleMapTo<PointsRegistRecordOutputDto>()).ToList()
                ?? new List<PointsRegistRecordOutputDto>();

            list = list.OrderByDescending(x => x.RegistTime).ToList();
            var userInfos = _context.UserInfos.Where(x => list.Select(x => x.UserId).Contains(x.Id) || list.Select(x => x.RegisterId).Contains(x.Id))?.ToList();

            var rtnList = new List<CalendarRegistRecordDto>();
            foreach (var item in list)
            {
                item.UserName = userInfos.First(x => x.Id == item.UserId).UserName;
                item.RegisterName = userInfos.First(x => x.Id == item.RegisterId).UserName;

                var hisElement = rtnList.FirstOrDefault(x => x.RecordDateTime == item.RegistTime.Date);
                if (hisElement != null)
                {
                    hisElement.Data.Add(item);
                    if ((item.OperationType == OperationTypeEnum.Add && hisElement.Info == "娱乐") ||
                        (item.OperationType == OperationTypeEnum.Use && hisElement.Info == "学习"))
                    {
                        hisElement.Info = "学+娱";
                    }
                }
                else
                {
                    rtnList.Add(new CalendarRegistRecordDto()
                    {
                        RecordDateTime = item.RegistTime.Date,
                        Info = item.OperationType == OperationTypeEnum.Add ? "学习" : "娱乐",
                        Data = new List<PointsRegistRecordOutputDto>() { item }
                    });
                }
            }

            result.IsSuccess(rtnList);
            return result;
        }

        public List<int> GetWeekRegistStatics(OperationTypeEnum operationType, DateTime weekStart, Guid? userId = null)
        {
            if (userId == null)
            {
                userId = _context.GetUserInfoByToken<UserInfo>().Id;
            }
            var weekEnd = weekStart.AddDays(7);
            var registGroups = _context.PointsRegistRecords
                .Where(x => x.UserId == userId && x.OperationType == operationType
                && x.RegistTime >= weekStart && x.RegistTime < weekEnd)?.ToList().GroupBy(x => x.RegistTime.Date);

            if (registGroups == null)
            {
                return new List<int> { 0, 0, 0, 0, 0, 0, 0 };
            }

            var rtnLst = new List<int>();
            for (int i = 1; i <= 7; i++)
            {
                var registGroup = registGroups.FirstOrDefault(x => x.Key.DayOfWeek == (DayOfWeek)(i % 7));
                if (registGroup != null)
                {
                    rtnLst.Add(registGroup.Sum(x => x.RegistDuration));
                }
                else
                {
                    rtnLst.Add(0);
                }
            }
            return rtnLst;
        }
    }
}
